<script lang="ts" module>
    export const TabsContextKey = Symbol("TabsContextKey");
    export const useTabsContext = () => getContext<TabsContextData>(TabsContextKey);
</script>

<script lang="ts">
    import { getContext, onDestroy, onMount, setContext } from "svelte";
    import { useClassList } from "../utils/useClassList";
    import type { TabPaneProps, TabsContextData, TabsProps, TabStore } from "./Tabs";
    import { useDebounce } from "../utils/useDebounce";
    import { Dropdown, DropdownItem, DropdownMenu, type DropdownProps } from "../Dropdown";
    import Icon from "../Common/Icons/Icon.svelte";
    import { Tooltip } from "../Tooltip";
    import { getRandomIntInclusive } from "../utils/utils";
    import { useStyle } from "../utils/useStyle";

    let { activeName = $bindable(), ...props }: TabsProps = $props();
    const store = $state({
        tabs: [],
        moreList: [],
        morePrevList: [],
        moreNextList: [],
        scroll: false,
        scrollLeft: 0,
    } as TabStore);

    let line: any;
    let scroll: any;
    let header: any;

    const type = $derived(props.type ?? "line");
    const animation = props.animation ?? false;
    const align = props.align ?? "top";
    let lineUpdate = $state(0);

    const classList = $derived(
        useClassList(props, "cm-tabs", {
            [`cm-tabs-${type}`]: true,
            [`cm-tabs-${align}`]: !!align,
            "cm-tabs-overflow": store.scroll,
            "cm-tabs-centered": props.centered,
            "cm-tabs-bordered": props.bordered,
            "cm-tabs-with-animation": animation,
            "cm-tabs-keep-height": props.keepHeight,
        })
    );

    $effect(() => {
        activeName;
        const activeItem = header.querySelector(".cm-tabs-header-item-active");
        if (activeItem) {
            activeItem.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest", container: "nearest" });
        }
    });

    // 往前滚动
    const scrollPrev = () => {
        const prop = align === "top" || align === "bottom" ? "width" : "height";
        const scrollProp = align === "top" || align === "bottom" ? "scrollLeft" : "scrollTop";
        const scrollAttr = align === "top" || align === "bottom" ? "left" : "top";
        const scrollWidth = scroll.getBoundingClientRect()[prop];
        const left = scroll[scrollProp] - scrollWidth;
        const scrollParams = {
            [`${scrollAttr}`]: left,
            behavior: "smooth",
        };
        scroll.scrollTo(scrollParams);
    };

    // 往后滚动
    const scrollNext = () => {
        const prop = align === "top" || align === "bottom" ? "width" : "height";
        const scrollProp = align === "top" || align === "bottom" ? "scrollLeft" : "scrollTop";
        const scrollAttr = align === "top" || align === "bottom" ? "left" : "top";
        const scrollWidth = scroll.getBoundingClientRect()[prop];
        const left = scroll[scrollProp] + scrollWidth;
        const scrollParams = {
            [`${scrollAttr}`]: left,
            behavior: "smooth",
        };
        scroll.scrollTo(scrollParams);
    };

    export const addTab = (obj: TabPaneProps) => {
        store.tabs.push(obj);
        setTimeout(() => {
            activeName = obj.name;
        });
    };

    const onTabClick = (item: TabPaneProps) => {
        activeName = item.name;
        props.ontabclick?.({ ...item });
    };

    // more dropdown onSelect
    const onSelectByName = (name: string, item: any) => {
        onTabClick(item);
    };

    /**
     * 删除tab
     * @param name
     * @param e
     */
    const onRemove = (name: string, e: any) => {
        e.preventDefault && e.preventDefault();
        e.stopPropagation && e.stopPropagation();
        const lastIndex = store.tabs.findIndex((tab: TabPaneProps) => {
            return tab.name === name;
        });
        const newArr: TabPaneProps[] = store.tabs.filter((tab: TabPaneProps) => {
            return tab.name !== name;
        });
        if (activeName === name) {
            const willActiveIndex = Math.max(lastIndex - 1, 0);
            if (newArr.length) {
                onTabClick(newArr[willActiveIndex]);
            }
        }
        props.onremove?.(name);
        store.tabs = newArr;
    };

    /**
     * 内容滚动样式
     * @returns
     */
    const contextStyle = $derived.by(() => {
        let currentIndex: number = 0;
        store.tabs.forEach((atab: any, index: number) => {
            if (atab.name === activeName) {
                currentIndex = index;
            }
        });

        const s: any = {
            transform: `translate(${-currentIndex * 100}%, 0)`,
        };

        if (props.duration !== undefined && typeof props.duration === "number") {
            s["transition-duration"] = props.duration + "ms";
        }

        return useStyle({}, s);
    });

    // 滚动的时候计算是否被遮盖
    const onScroll = () => {
        observeIntersectionFunc();
        lineUpdate = getRandomIntInclusive(0, 1000000);
    };

    // 鼠标滚轮事件
    const onMouseWheel = (e: any) => {
        if (!store.scroll) {
            return;
        }
        e.preventDefault && e.preventDefault();
        if (align === "top" || align === "bottom") {
            if (e.deltaY > 0) {
                scroll.scrollLeft += 50;
            } else {
                scroll.scrollLeft -= 50;
            }
        } else {
            if (e.deltaY > 0) {
                scroll.scrollTop += 50;
            } else {
                scroll.scrollTop -= 50;
            }
        }
    };

    onMount(() => {
        const bo = new ResizeObserver((entries) => {
            updateScroll();
        });
        bo.observe(scroll);
        bo.observe(header);
        updateScroll();

        onDestroy(() => {
            bo.disconnect();
        });
    });

    // tab是否被遮盖
    const observeIntersection = () => {
        const ob = new IntersectionObserver(
            (entries) => {
                const names: string[] = [];
                let firstDisplayItemName = "";
                entries.forEach((entry) => {
                    const ele = entry.target;
                    if (entry.intersectionRatio < 0.98) {
                        names.push((ele as HTMLElement).dataset.name!);
                    } else {
                        if (!firstDisplayItemName) {
                            firstDisplayItemName = (ele as HTMLElement).dataset.name!;
                        }
                    }
                });
                const firstDisplayIndex = store.tabs.findIndex((item) => item.name === firstDisplayItemName)!;

                const list: TabPaneProps[] = [];
                const prevList: TabPaneProps[] = [];
                const nextList: TabPaneProps[] = [];
                names.forEach((name) => {
                    const index = store.tabs.findIndex((item) => item.name === name)!;
                    const item = store.tabs[index];
                    list.push(item);
                    if (index < firstDisplayIndex) {
                        prevList.push(item);
                    } else {
                        nextList.push(item);
                    }
                });
                store.moreList = list;
                store.morePrevList = prevList;
                store.moreNextList = nextList;
                ob.disconnect();
            },
            {
                root: scroll,
                rootMargin: "0px",
                threshold: 0,
            }
        );
        const items = header?.querySelectorAll(".cm-tabs-header-item");
        items.forEach((item: any) => {
            ob.observe(item);
        });
    };

    const observeIntersectionFunc = useDebounce(observeIntersection, 200);

    const updateScroll = useDebounce(() => {
        if (!(scroll && header)) {
            return;
        }
        const prop = align === "top" || align === "bottom" ? "width" : "height";
        const scrollWidth = scroll.getBoundingClientRect()[prop];
        const headerWidth = header.getBoundingClientRect()[prop];

        if (headerWidth > scrollWidth && !store.scroll) {
            store.scroll = true;
            lineUpdate = getRandomIntInclusive(0, 1000000);
        }
        if (headerWidth < scrollWidth && store.scroll) {
            store.scroll = false;
            lineUpdate = getRandomIntInclusive(0, 1000000);
        }

        // 是否被遮盖
        observeIntersectionFunc();
    }, 100);

    // 更新下线的移动位置
    const lineStyle = $derived.by(() => {
        lineUpdate;
        if (type === "line") {
            if (!header) return;
            let currentIndex: number = 0;
            store.tabs.forEach((atab: any, index: number) => {
                if (atab.name === activeName) {
                    currentIndex = index;
                }
            });

            const eles = header.querySelectorAll(".cm-tabs-header-item");
            const ele = eles[currentIndex];
            if (!ele) {
                return;
            }
            const close = ele.querySelector(".cm-tabs-close");

            const rect = ele.getBoundingClientRect();
            const wrapRect = scroll.getBoundingClientRect();
            const scrollProp = align === "top" || align === "bottom" ? "scrollLeft" : "scrollTop";
            const scrollOffset = scroll[scrollProp];
            const headerLeft = scrollOffset + (align === "top" || align === "bottom" ? rect.left - wrapRect.left : rect.top - wrapRect.top);
            const width = rect.width;
            const height = rect.height;
            if (align === "top" || align === "bottom") {
                line.style.width = `${width}px`;
                line.style.left = `${headerLeft}px`;
            } else {
                line.style.height = `${height}px`;
                line.style.top = `${headerLeft}px`;
            }
            return useStyle(
                {},
                align === "top" || align === "bottom"
                    ? {
                          width: `${width}px`,
                          left: `${headerLeft}px`,
                      }
                    : {
                          height: `${height}px`,
                          top: `${headerLeft}px`,
                      }
            );
        }
    });

    const prevDropdownAlign = (): DropdownProps["align"] => {
        if (align === "bottom") {
            return props.arrowPosition === "end" ? "topRight" : "topLeft";
        }
        if (align === "left") {
            return props.arrowPosition === "end" ? "rightBottom" : "rightTop";
        }
        if (align === "right") {
            return props.arrowPosition === "end" ? "leftBottom" : "leftTop";
        }
        if (align === "top") {
            return props.arrowPosition === "end" ? "bottomRight" : "bottomLeft";
        }
    };

    const nextDropdownAlign = (): DropdownProps["align"] => {
        if (align === "bottom") {
            return props.arrowPosition === "start" ? "topLeft" : "topRight";
        }
        if (align === "left") {
            return props.arrowPosition === "start" ? "rightTop" : "rightBottom";
        }
        if (align === "right") {
            return props.arrowPosition === "start" ? "leftTop" : "leftBottom";
        }
        if (align === "top") {
            return props.arrowPosition === "start" ? "bottomLeft" : "bottomRight";
        }
    };

    const moreDropdownAlign = (): DropdownProps["align"] => {
        if (align === "bottom") {
            return "topRight";
        }
        if (align === "left") {
            return "rightBottom";
        }
        if (align === "right") {
            return "leftBottom";
        }
        if (align === "top") {
            return "bottomRight";
        }
    };

    setContext(TabsContextKey, {
        // 初始化添加
        addTabPane: (tabPane: TabPaneProps) => {
            const find = store.tabs.find((item) => item.name === tabPane.name);
            if (!find) {
                // 初始化没有activeName，则默认第一个
                if (!activeName) {
                    activeName = tabPane.name;
                }
                store.tabs.push(tabPane);
            }
        },
        removeTabPane: (name: string) => {
            const index = store.tabs.findIndex((item) => item.name === name);
            if (index !== -1) {
                store.tabs.splice(index, 1);
            }
        },
    } as TabsContextData);
</script>

{#snippet renderMore(arr: TabPaneProps[])}
    <DropdownMenu>
        {#each arr as item (item.name)}
            <DropdownItem data={item} name={item.name} icon={item.icon}>
                {#if typeof item.title === "function"}
                    {@render item.title()}
                {:else}
                    {item.title}
                {/if}
                {#if item.closeable}
                    <Icon icon="times" onclick={onRemove.bind(null, item.name)} size={12} />
                {/if}
            </DropdownItem>
        {/each}
    </DropdownMenu>
{/snippet}
{@render props.children?.()}
<div class={classList} style={props.style}>
    <div class="cm-tabs-header-wrap">
        {#if props.prepend}
            <div class="cm-tabs-extra-prepend">
                {@render props.prepend()}
            </div>
        {/if}
        {#if props.arrowPosition === "start" || props.arrowPosition === "both"}
            <Dropdown disabled={!props.more} align={prevDropdownAlign()} onselect={onSelectByName} class={"cm-tabs-more-dropdown"}>
                {#snippet menu()}
                    {@render renderMore(store.morePrevList)}
                {/snippet}
                <!-- svelte-ignore a11y_click_events_have_key_events -->
                <!-- svelte-ignore a11y_no_static_element_interactions -->
                <div
                    class={{
                        "cm-tabs-prev": true,
                        "cm-tabs-prev-disabled": !store.morePrevList.length,
                    }}
                    onclick={scrollPrev}
                >
                    {#if align === "top" || align === "bottom"}
                        <Icon icon="chevronLeft" size={16} />
                    {:else}
                        <Icon icon="chevronUp" size={16} />
                    {/if}
                </div>
            </Dropdown>
        {/if}
        {#if props.arrowPosition === "start"}
            <Dropdown disabled={!props.more} align={nextDropdownAlign()} onselect={onSelectByName} class="cm-tabs-more-dropdown">
                {#snippet menu()}
                    {@render renderMore(store.moreNextList)}
                {/snippet}
                <!-- svelte-ignore a11y_click_events_have_key_events -->
                <!-- svelte-ignore a11y_no_static_element_interactions -->
                <div
                    class={{
                        "cm-tabs-next": true,
                        "cm-tabs-next-disabled": !store.moreNextList.length,
                    }}
                    onclick={scrollNext}
                >
                    {#if align === "top" || align === "bottom"}
                        <Icon icon="chevronRight" size={16} />
                    {:else}
                        <Icon icon="chevronDown" size={16} />
                    {/if}
                </div>
            </Dropdown>
        {/if}
        <Dropdown trigger="contextMenu" transfer handler=".cm-tabs-header-item" align="bottom" onselect={props.onselectcontextmenu} {...props.dropdownProps}>
            {#snippet menu()}
                {@render props.contextMenu?.()}
            {/snippet}
            <div class="cm-tabs-scroll" bind:this={scroll} onscroll={onScroll} onwheel={onMouseWheel}>
                <div class="cm-tabs-active-line" bind:this={line} style={lineStyle}></div>
                <ul class="cm-tabs-header" bind:this={header}>
                    {#each store.tabs as item}
                        {@const className = {
                            "cm-tabs-header-item": true,
                            [`cm-tabs-header-item-${props.buttonTheme}`]: !!props.buttonTheme,
                            "cm-tabs-header-item-active": item.name === activeName,
                            "cm-tabs-header-item-disabled": item.disabled,
                        }}
                        {#snippet el()}
                            <!-- svelte-ignore a11y_click_events_have_key_events -->
                            <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
                            <li class={className} data-name={item.name} onclick={onTabClick.bind(null, item)} oncontextmenu={props.oncontextmenu?.bind(null, { ...item })}>
                                <div>
                                    {@render item.icon?.()}
                                    <span class="cm-tabs-header-item-text" style={`max-width: ${props.maxTabSize}px`}>{item.title}</span>
                                    {#if item.closeable}
                                        <Icon icon="times" onclick={onRemove.bind(null, item.name)} class="cm-tabs-close" size={12} />
                                    {/if}
                                </div>
                            </li>
                        {/snippet}
                        {#if props.tooltip}
                            <Tooltip content={item.title} align="bottom" theme="light" {...props.tooltipProps}>
                                {@render el()}
                            </Tooltip>
                        {:else}
                            {@render el()}
                        {/if}
                    {/each}
                </ul>
            </div>
        </Dropdown>
        {#if store.scroll && props.more && store.moreList.length && !props.arrowPosition}
            <Dropdown align={moreDropdownAlign()} {...props.moreDropdownProps} onselect={onSelectByName} class={"cm-tabs-more-dropdown"}>
                {#snippet menu()}
                    {@render renderMore(store.moreList)}
                {/snippet}
                <div class="cm-tabs-more">
                    {#if props.renderMore}
                        {@render props.renderMore?.()}
                    {:else}
                        <Icon icon="ellipsis" size={14} />
                    {/if}
                </div>
            </Dropdown>
        {/if}
        {#if props.arrowPosition === "end"}
            <Dropdown disabled={!props.more} align={prevDropdownAlign()} onselect={onSelectByName} class={"cm-tabs-more-dropdown"}>
                {#snippet menu()}
                    {@render renderMore(store.morePrevList)}
                {/snippet}
                <!-- svelte-ignore a11y_click_events_have_key_events -->
                <!-- svelte-ignore a11y_no_static_element_interactions -->
                <div
                    class={{
                        "cm-tabs-prev": true,
                        "cm-tabs-prev-disabled": !store.morePrevList.length,
                    }}
                    onclick={scrollPrev}
                >
                    {#if align === "top" || align === "bottom"}
                        <Icon icon="chevronLeft" size={16} />
                    {:else}
                        <Icon icon="chevronUp" size={16} />
                    {/if}
                </div>
            </Dropdown>
        {/if}
        {#if props.arrowPosition === "end" || props.arrowPosition === "both"}
            <Dropdown disabled={!props.more} align={nextDropdownAlign()} onselect={onSelectByName} class={"cm-tabs-more-dropdown"}>
                {#snippet menu()}
                    {@render renderMore(store.moreNextList)}
                {/snippet}
                <!-- svelte-ignore a11y_click_events_have_key_events -->
                <!-- svelte-ignore a11y_no_static_element_interactions -->
                <div
                    class={{
                        "cm-tabs-next": true,
                        "cm-tabs-next-disabled": !store.moreNextList.length,
                    }}
                    onclick={scrollNext}
                >
                    {#if align === "top" || align === "bottom"}
                        <Icon icon="chevronRight" size={16} />
                    {:else}
                        <Icon icon="chevronDown" size={16} />
                    {/if}
                </div>
            </Dropdown>
        {/if}
        {#if props.append}
            <div class="cm-tabs-extra-append">
                {@render props.append()}
            </div>
        {/if}
    </div>
    <div class="cm-tabs-content-wrap">
        <div class="cm-tabs-content" style={contextStyle}>
            {#each store.tabs as item}
                {@const panelClass = useClassList(item, "cm-tab-panel", {
                    "cm-tab-panel-active": item.name === activeName,
                })}
                <div class={panelClass}>
                    {#if typeof item.children === "function"}
                        {@render item.children?.()}
                    {:else}
                        {item.children}
                    {/if}
                </div>
            {/each}
        </div>
    </div>
</div>
